
/*
 *
 * This is the main NDMP procedures, the heart of the project
 * This impliemts all the required NDMP operations like, device open, scsi_execute_cdb, making connectios, closing connections, closing devices,
 * authenticating NDMP user, etc.
 * 
 *
 *	Author: Aboo Valappil
 *	Date: 2/10/2008
 *	Based on: None
 */

#include "ndmp_main.h"

static int ndmp_tcp_connect ( struct ndmp_scsi_session *session);
static int ndmp_recvmsg(struct ndmp_scsi_session *session, struct kvec *iov, int niov);
static int ndmp_recv_rm_header (struct ndmp_scsi_session *session);
static int ndmp_sendmsg(struct ndmp_scsi_session *session, struct kvec *iov, int niov, int len);

static int ndmp_send_xdr_buffer(struct ndmp_scsi_session *session);
static int ndmp_tape_open ( struct ndmp_scsi_session *session);
static int ndmp_recv_header(struct ndmp_scsi_session *session, unsigned long seq);
static int ndmp_recv_header(struct ndmp_scsi_session *session, unsigned long seq);
static int __ndmp_recv_header (struct ndmp_scsi_session *session);
static int ndmp_recv_connected(struct ndmp_scsi_session *session);
static int ndmp_prepare_ndmp_head(struct ndmp_scsi_session *session, int code, unsigned long seq);
static int ndmp_recv_basic_response(struct ndmp_scsi_session *session, int rsize);
static char *ndmperror(int code);


/* ndmp_tcp_connect - Make TCP connections for the NDMP session */
static int ndmp_tcp_connect ( struct ndmp_scsi_session *session) 
{

	struct sockaddr_in *addr;
	int arg = 1;
	int s;

	addr=(struct sockaddr_in*)&(session->addr);

	addr->sin_addr.s_addr=in_aton(session->ndmp_server);
	addr->sin_family=AF_INET;
	addr->sin_port=htons(NDMP_TCP_PORT);

	s=sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP,&session->socket);

	if ( s < 0 ) {
		printk("ndmphba:%d: Unable to create TCP socket\n", session->session_id);
		return NDMP_NET_IO_CONNECT_FAIL;
	}

	session->socket->sk->sk_allocation = GFP_ATOMIC;

/* 	Is setting the window size required? Future plans!
	if ( session->tcpwnd) {
		s=sock_setsockopt(session->socket, SOL_SOCKET, SO_RCVBUF, (char *)&session->tcpwnd, sizeof(session->tcpwnd));
		s+=sock_setsockopt(session->socket, SOL_SOCKET, SO_SNDBUF, (char *)&session->tcpwnd, sizeof(session->tcpwnd));
		if (s) printk ("dfsfsdfsdf :\n");
	} */

	printk("ndmphba:%d: Initiating Connection to NDMP Server: %s\n", session->session_id, session->ndmp_server);

/* Change the state to progress for the watchdog to start the timer */
	ndmp_change_session_state(session,HBA_STATE_CONNECTION, _NDMP_STAT_IN_PROGRESS);
	s=session->socket->ops->connect(session->socket, (struct sockaddr *)addr, sizeof(struct sockaddr),0);

	if (s) {
		ndmp_close_socket(session);
		printk("ndmphba:%d: Unable to connect to NDMP server : %s\n", session->session_id, session->ndmp_server);
		return NDMP_NET_IO_CONNECT_FAIL;
	}

/* Enable the quick ACK mode as delayed ACK and EMC Celerra Filer combination may affect the throughput badly 
 * anyway, the quick ACK mode does not seem working as the TCP stacks keep disabling it!
*/
	s=sock_setsockopt(session->socket, IPPROTO_TCP, TCP_QUICKACK, (char *)&arg, sizeof(arg));

	printk("ndmphba:%d: Connected to NDMP Server %s\n", session->session_id, session->ndmp_server); 

/* Change the connection state to ready (done) */
	ndmp_change_session_state(session,HBA_STATE_CONNECTION, _NDMP_STAT_DONE);
	return NDMP_NET_IO_SUCCESS;
}

/* ndmp_recv_rm_header - Receives ONC RPC XDR record marking headers (RM)
 *
 * receives the RM header and updates the RM in the session structure
 *
 */
static int ndmp_recv_rm_header (struct ndmp_scsi_session *session)
{

	unsigned int s;
	struct kvec iov;
        struct msghdr msg;
	int ret;

	iov.iov_base=&(session->rmh);
	iov.iov_len=4;

        memset(&msg, 0, sizeof(msg));
      	s = kernel_recvmsg(session->socket, &msg, &iov, 1, iov.iov_len, MSG_WAITALL);
        if (signal_pending(current)) {
		printk("ndmphba:%d: Socket read was interrupted\n", session->session_id);
		ret=NDMP_NET_IO_INTR;
		goto error;
	} else if (s != 4) {
		printk("ndmphba:%d: Unexpected read size, attempted read: %u, read: %u\n", session->session_id, s,4);
                ret=NDMP_NET_IO_ERR;
		goto error;
        }

	session->rmh=ntohl(session->rmh);
	session->last_fragment=session->rmh >> 31;
	session->rmh &= 0x7FFFFFFF;
	return NDMP_NET_IO_SUCCESS;

error: 
	ndmp_close_socket(session);
	return ret;

}	

/* ndmp_simple_read - reads data from the socket to IO vector 
 * once it finishes some checks, it just invoke the kernel API kernel_recvmsg
 */
static int ndmp_simple_read ( struct ndmp_scsi_session *session, struct kvec *iov)
{

	struct msghdr msg;
	int s,l;

	if ( ndmp_session_state (session, HBA_STATE_CONNECTION) != _NDMP_STAT_DONE ) {
		 return NDMP_NET_IO_ERR;
        }

		if ( (!iov->iov_len) ) {
			printk("ndmphba:%d: ndmp_simple_read attempting to read 0 bytes\n", session->session_id);
			return NDMP_NET_IO_SUCCESS;
		}

		memset(&msg, 0, sizeof(msg));

/* Make sure that the read does not happen beyond RM boundaries. It is unlikely as ndmp_recvmsg will marshal it properly 
   Should be using compiler directive unlikely? */
	      if ( (session->last_fragment && session->rmh < iov->iov_len) ) {
			printk("ndmphba:%d: Read request exceeds the RM fragment size\n", session->session_id);
			return NDMP_NET_IO_ERR;
	      }


	      l=iov->iov_len;

              s = kernel_recvmsg(session->socket, &msg, iov, 1, iov->iov_len, MSG_WAITALL);
              if (signal_pending(current)) {
                        printk("ndmphba:%d: Socket read was interrupted\n", session->session_id);
                        return NDMP_NET_IO_INTR;
               } else if (s != l) {
                        printk("ndmphba:%d: Unexpected read size, attempted read: %d, read: %d\n", session->session_id, s,l);
                        return NDMP_NET_IO_ERR;
               } else {
                        session->rmh-=s;
                        return NDMP_NET_IO_SUCCESS;
               }
}

/* ndmp_recv_header - Receives the NDMP header from the socket 
 *
 * This is wrapper function for __ndmp_recv_header as this would ignore every response other than expected command sequence.
 *
 */
static inline int ndmp_recv_header (struct ndmp_scsi_session *session, unsigned long seq)
{
	int r;

	do {
		if ( (r=__ndmp_recv_header(session)) ) return r;
	} while (session->nh.reply_sequence != seq );
	return NDMP_NET_IO_SUCCESS;
}

/* __ndmp_recv_header - Receves the NDMP header and stores in to session structure  */
static int __ndmp_recv_header (struct ndmp_scsi_session *session)
{

	int t,l,s;
        struct kvec iov;
	void *residual=NULL;

/* This loop is required to get rid of any residual information from the previous command which was not take care by the XDR (ndmp_xdr.c) routines 
 * This loop is also unlikely unless the NDMP server behaves strange
 */
	while ( session->rmh || (!session->last_fragment) ) {

		if ( ! residual ) {
			if ( ! (residual = kmalloc(PAGE_SIZE,GFP_KERNEL))) {
				printk("ndmphba: Unable to allocate kernel memory\n");
				ndmp_close_socket(session);
				return NDMP_NET_IO_ERR;
			}
		}

		if ( !session->last_fragment ) ndmp_recv_rm_header(session);
        	iov.iov_base=residual;
		if ( session->rmh > PAGE_SIZE ) iov.iov_len=PAGE_SIZE;
		else iov.iov_len=session->rmh;
		l=iov.iov_len;
		if ((s=ndmp_simple_read(session, &iov))) {  kfree(residual); return s; }
	}

	if ( residual ) kfree(residual);

/* Next read from the socket should give us the NDMP header for the message */
	xdr_init(&session->xdr, XDR_DECODE);
        iov.iov_base=&(session->nh);
        iov.iov_len=sizeof(struct ndmp_header);
        if ( (t=ndmp_recvmsg (session, &iov,1)) !=  NDMP_NET_IO_SUCCESS ) return t;

	session->nh.sequence=ntohl(session->nh.sequence);
	session->nh.time_stamp=ntohl(session->nh.time_stamp);
	session->nh.message_type=ntohl(session->nh.message_type);
	session->nh.message_code=ntohl(session->nh.message_code);
	session->nh.reply_sequence=ntohl(session->nh.reply_sequence);
	session->nh.error_code=ntohl(session->nh.error_code);

	if ( session->nh.error_code ) {
		printk("ndmphba:%d: Server returned NDMP Error: %s\n", session->session_id, ndmperror(session->nh.error_code));
                ndmp_close_socket(session);
                return NDMP_NET_IO_ERR;
        }

        return NDMP_NET_IO_SUCCESS;
}


/* ndmp_recvmsg - Receives NDMP messages from the socket
 * This routine looks complicated (and ugly/slow?) than one may expect, it is complicated just because of the record marking headers
 * as some NDMP servers uses multiple records in a single NDMP messages!
 * 
 * This calls ndmp_simple_read eventually after taking care of the record markings
 */
static int ndmp_recvmsg(struct ndmp_scsi_session *session, struct kvec *iov, int niov)
{
        int s;
	int ret;
	int i;
	int j;
	struct kvec ii,iii;


	for ( i=0;i<niov;i++) {
		ii.iov_len=iov[i].iov_len;
		ii.iov_base=iov[i].iov_base;
		for(j=i;j<MAX_XDR_RECORDS;j++) {
              		if ( ! session->rmh ) {
				if ( (s=ndmp_recv_rm_header(session)) != NDMP_NET_IO_SUCCESS ) return s;
			}
			if ( session->rmh < ii.iov_len ) {
				iii.iov_len=session->rmh;
              			iii.iov_base=iov[i].iov_base;
				ii.iov_len-=session->rmh;
              			ii.iov_base+=session->rmh;
				if ((s=ndmp_simple_read(session, &iii))) {
					ret=s;
					goto error;
				}
			} else {
				if ((s=ndmp_simple_read(session, &ii))) {
					ret=s;
					goto error;
				}
				break;
			}
		}
		if ( j == MAX_XDR_RECORDS ) {
			printk("ndmphba:%d: Too many XDR records in response\n", session->session_id);
			ret= NDMP_NET_IO_ERR;
			goto error;
		}

	}


	ret=NDMP_NET_IO_SUCCESS;;
	return ret;
		
error:
	ndmp_close_socket(session);
	return ret;
}

/*
 * ndmp_sendmsg - Sends messages to the NDMP server
 * This is not complicated as receive message as the initiator uses on one XDR record and the largest IO is limited to 128KB,  NDMP_SCSI_MAX_SECTORS
 */
static int ndmp_sendmsg(struct ndmp_scsi_session *session, struct kvec *iov, int niov, int len)
{
        struct msghdr msg;
        int s;
	int ret;

	if ( ndmp_session_state (session, HBA_STATE_CONNECTION) != _NDMP_STAT_DONE ) {
		 ret=NDMP_NET_IO_ERR;
                goto error;
        }

        memset(&msg, 0, sizeof(msg));
        s = kernel_sendmsg(session->socket, &msg, iov, niov, len);
        if (signal_pending(current)) {
		printk("ndmphba:%d: Socket write was interrupted\n", session->session_id);
		ret=NDMP_NET_IO_INTR;
		goto error;
	} else if (s != len) {
		printk("ndmphba:%d: Unable to write %d to socket, written: %d\n", session->session_id,len, s);
                ret=NDMP_NET_IO_ERR;
		goto error;
        }

        return NDMP_NET_IO_SUCCESS;

error:
	ndmp_close_socket(session);
	return ret;
}
/* these replies have just one simple return code, why not use ndmp_connect_client_auth_reply? Just lazy. */
#define xdr_ndmp_generic_reply xdr_ndmp_connect_client_auth_reply
#define ndmp_generic_reply ndmp_connect_client_auth_reply

/* ndmp_prepare_ndmp_head - Prepares the NDMP header for the next message to NDMP server
 * All the required values are in the session strcuture
 */
static inline int ndmp_prepare_ndmp_head(struct ndmp_scsi_session *session, int code, unsigned long seq)
{

        ndmp_header nh;

	xdr_init(&session->xdr, XDR_ENCODE);
	nh.sequence=seq;
	nh.time_stamp=session->nh.time_stamp;
        nh.message_type=NDMP_MESSAGE_REQUEST;
	nh.message_code=code;
	nh.reply_sequence=0;
        nh.error_code=0;
	if ( ! xdr_ndmp_header(&session->xdr,&nh) ) return NDMP_XDR_ERROR;
	return 0;
}

/*
 * ndmp_recv_basic_response - Receives the basic (other than the IO data) NDMP response
 */
static inline int ndmp_recv_basic_response(struct ndmp_scsi_session *session, int rsize)
{
        struct kvec kv;
	int r;
	XDR *xdr=&session->xdr;

        kv.iov_len=rsize;
        kv.iov_base=xdr->buffer+xdr->pos;
        if ( (r=ndmp_recvmsg(session, &kv, 1))) return r;
	return NDMP_NET_IO_SUCCESS;

}

/*
 * ndmp_close_device - Close an open tape drive or robotic arm 
 */
int ndmp_close_device ( struct ndmp_scsi_session *session)
{
	ndmp_generic_reply rep;
	int code;
        int r;

	if ( ndmp_session_state(session, HBA_STATE_DEVICE) != _NDMP_STAT_DONE ) return 0;

	ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_IN_PROGRESS);

	session->seq_number++;

	if ( session->device_type == TAPE_DEVICE ) code=NDMP_TAPE_CLOSE;
	else code=NDMP_SCSI_CLOSE;

        if ( ndmp_prepare_ndmp_head(session,code,session->seq_number) ) goto xdr_error;

        if ( (r=ndmp_send_xdr_buffer(session))) return r;

        if  ( (r=ndmp_recv_header(session,session->seq_number))) return r;

	if ( (r=ndmp_recv_basic_response(session,sizeof(ndmp_connect_client_auth_reply))) ) return r;

	if ( ! xdr_ndmp_generic_reply(&session->xdr,&rep) ) goto xdr_error;

	ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_NR);

	return 0;

xdr_error:
	printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_close_device\n", session->session_id);
       ndmp_close_socket(session);
       return NDMP_XDR_ERROR;

}

/* ndmp_kunmap - Just unmap mapped kernel virtual memory for a SCSI command 
 * Calls this when a SCSI command is completed, otherwise, the kernel may run out of virtual address with HIGHMEM enabled
 */
static inline void ndmp_kunmap(XDR *xdr)
{
	struct page **kpages;

	kpages=xdr->kpages;
	
	for (;*kpages;kpages++)  { 
//		printk("Unmapping %x\n", *kpages);
		kunmap(*kpages);
	}

	*xdr->kpages=NULL;
	
}

/* ndmp_exec_scsi_cmd - Execute a SCSI command
 * @task - is a SCSI command queued by queuecommand routine
 */
int ndmp_exec_scsi_cmd ( struct ndmp_scsi_task *task)
{
	ndmp_execute_cdb_request req;
	int sgcount;
	int r;
	struct ndmp_scsi_session *session;
	int code;

	session=task->session;

	sgcount=scsi_sg_count(task->sc);

        if ( session->device_type == TAPE_DEVICE ) code=NDMP_TAPE_EXECUTE_CDB;
	else code=NDMP_SCSI_EXECUTE_CDB;

	if ( task->sc->sc_data_direction == DMA_TO_DEVICE ) {
		req.flags=NDMP_SCSI_DATA_OUT;
		req.datain_len=0;
		req.dataout.dataout_len=scsi_bufflen(task->sc);
		req.dataout.dataout_val=(char *)scsi_sglist(task->sc);
		session->xdr.residual = req.dataout.dataout_len;
	} else {
		req.flags=NDMP_SCSI_DATA_IN;
		req.datain_len=scsi_bufflen(task->sc);
		req.dataout.dataout_len=0;
		session->xdr.residual = req.datain_len;
	}
	req.timeout=0;
	req.cdb.cdb_len=task->sc->cmd_len;
	req.cdb.cdb_val=task->sc->cmnd;

/* Once the req structe is ready, call the XDR routing (ndmp_xdr.c) to prepare the IO vector */
        if ( ndmp_prepare_ndmp_head(session,code, task->seq_number) ) goto xdr_error;

	if ( ! xdr_ndmp_execute_cdb_request(&session->xdr, &req, sgcount) ) {
		r=NDMP_XDR_ERROR;
		printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_exec_scsi_cmd\n", session->session_id);
		goto unmap_scsi_sg;
	}

/* The IO vector is ready from the XDR routine, now send the IO vector */
	if ( (r=ndmp_send_xdr_buffer(session))) goto unmap_scsi_sg;

	r=0;

unmap_scsi_sg:
	ndmp_kunmap(&session->xdr);
	return r;

xdr_error:
	printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_exec_scsi_cmd\n", session->session_id);
       ndmp_close_socket(session);
       return NDMP_XDR_ERROR;

}

/*
 * ndmp_do_scsi_inquiry_and_open_device - Probes the device and change the type accordingly.
 * When a device is passed to the drive, it has no idea if the device is a tape drive or robotic arm. So this routine will first probe it 
 * and set the type accordingly.
 *
 * May be this should be done from user space, but for time being leave it here.
 */
int ndmp_do_scsi_inquiry_and_open_device (struct ndmp_scsi_session *session)
{
        ndmp_execute_cdb_request s_req;
        ndmp_execute_cdb_reply rep;
        int code;
	char cmnd[16];
	char response[96];
	int r;

	ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_IN_PROGRESS);

	if ((r=ndmp_tape_open(session))) return r;

        s_req.flags=NDMP_SCSI_DATA_IN;
        s_req.timeout=0;
        s_req.datain_len=96;
        s_req.cdb.cdb_len=6;

        cmnd[0]=0x12;
        cmnd[1]=0;
        cmnd[2]=0;
        cmnd[3]=0;
        cmnd[4]=96;
        cmnd[5]=0;

        s_req.cdb.cdb_val=cmnd;

        s_req.dataout.dataout_len=0;
        s_req.dataout.dataout_val=NULL;


        code=NDMP_SCSI_EXECUTE_CDB;

	session->seq_number++;

        if ( ndmp_prepare_ndmp_head(session,code, session->seq_number) ) goto error;

        if ( ! xdr_ndmp_execute_cdb_request(&session->xdr, &s_req, 0) ) {
                printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_recv_scsi_cmd\n", session->session_id);
		goto error;
        }

        if ( (r=ndmp_send_xdr_buffer(session))) return r;

        if  ( (r=ndmp_recv_header(session,session->seq_number))) return r;

        if ( (r=ndmp_recv_basic_response(session,16)) ) return r;

	rep.datain.datain_val=(char *)response;

	if ( ! xdr_ndmp_execute_cdb_reply(&session->xdr, &rep, 0) ) {
                printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_recv_scsi_cmd\n", session->session_id);
		goto error;
        }

        if ( rep.status || rep.error ) {
                printk("ndmphba:%d: Inquiry failed on %s, can not continue\n", session->session_id, session->scsi_device);
		goto error;
        }

        if (( r=ndmp_recvmsg(session, session->xdr.kv, session->xdr.kvcount) )) return r;

	if ( *response == 1 ) {
		ndmp_close_device(session);
		session->device_type=TAPE_DEVICE;
		ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_IN_PROGRESS);
		if ((r=ndmp_tape_open(session))) return r;
	}
	else if ( *response != 8 ) {
		printk("ndmphba:%d: Inquiry, invalid device type or device not attached\n", session->session_id);
		goto error;
	}

	printk("ndmphba:%d:Tape drive:%s opened\n", session->session_id,session->scsi_device);

	return 0;

error:

         ndmp_close_device(session);
         ndmp_close_socket(session);
         return NDMP_NET_IO_ERR;
		
}

/* ndmp_recv_scsi_cmd - recevie a SCSI command response from the socket 
 *
 * This routine is bit more complicated than execute scsi command due to multiple data sets in the response ( payload + sense buffer )
 * So multiple calls are necessay to perform the IO as all the IO vector can not be prepared by single call to XDR routine
 *
 */
int ndmp_recv_scsi_cmd (struct ndmp_scsi_task *task)
{
        ndmp_execute_cdb_reply rep;
        int sgcount;
        int r;
	XDR *xdr;
	struct ndmp_scsi_session *session;

	session=task->session;

	xdr=&session->xdr;
        sgcount=scsi_sg_count(task->sc);

	if  ( (r=ndmp_recv_header(session,task->seq_number))) return r;

/* Receive the basic response data (excluding the IO payload / sense buffer */
	if ( (r=ndmp_recv_basic_response(session,16)) ) return r;

	rep.datain.datain_val=(char *)scsi_sglist(task->sc);

/* Call the XDR routine to populate the rep structe and prepare the IO vector for payload*/
        if ( ! xdr_ndmp_execute_cdb_reply(xdr, &rep, sgcount) ) {
		r=NDMP_XDR_ERROR;
		printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_recv_scsi_cmd\n", session->session_id);
		goto unmap_scsi_sg;
	}

	if ( rep.error != NDMP_NO_ERR ) {
		printk("ndmphba:%d: Error returned from the NDMP server for exec_scsi_cmd task: %d, Error: %s\n",session->session_id, task->seq_number, ndmperror(rep.error));
		ndmp_close_device(session);
		r=NDMP_NET_IO_ERR;
		goto unmap_scsi_sg;
	}

/* Include the length of the sense buffer in the IO vector */
	xdr->kv[xdr->kvcount].iov_len=4;
	xdr->kv[xdr->kvcount].iov_base=xdr->buffer+xdr->pos;
	xdr->kvcount++;
	xdr->kvlen+=4;

/* Receive the IO payload */
        if (( r=ndmp_recvmsg(session, xdr->kv, xdr->kvcount) )) goto unmap_scsi_sg;

/* The IO complete now the kernel virtual address can be unmapped */
	ndmp_kunmap(xdr);
//	scsi_set_resid(task->sc, xdr->residual);


/* This is just for the sense buffer (if any) */
	if (!xdr_ndmp_sg (xdr, (struct scatterlist *)task->sc->sense_buffer,0,&rep.ext_sense.ext_sense_len)) goto xdr_error;

	task->sc->result=rep.status;

	if ( ! rep.ext_sense.ext_sense_len ) return 0;

        if (( r=ndmp_recvmsg(session, xdr->kv, xdr->kvcount) )) return r;

	return 0;

unmap_scsi_sg:
        ndmp_kunmap(xdr);
        return r;

xdr_error:
       printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_recv_scsi_cmd\n", session->session_id);
       ndmp_close_socket(session);
       return NDMP_XDR_ERROR;


}

/*
 * ndmp_auth - Do the NDMP authentication for the session
 */
int ndmp_auth ( struct ndmp_scsi_session *session)
{
	ndmp_connect_client_auth_request req;
	ndmp_connect_client_auth_reply rep;
	int r;

        if ( (r=ndmp_tcp_connect (session) )) return r;

        if ( (r=ndmp_recv_connected (session) )) {
                printk("ndmphba:%d: Uexpected response from the NDMP Server\n", session->session_id);
                return r;
        }
	ndmp_change_session_state(session, HBA_STATE_AUTH,_NDMP_STAT_IN_PROGRESS);

	if ( ndmp_prepare_ndmp_head(session,NDMP_CONNECT_CLIENT_AUTH,session->seq_number) ) goto xdr_error;
	
	req.auth_data.auth_type=NDMP_AUTH_TEXT;
	req.auth_data.ndmp_auth_data_u.auth_text.auth_id=session->username;
	req.auth_data.ndmp_auth_data_u.auth_text.auth_password=session->password;
	if ( ! xdr_ndmp_connect_client_auth_request(&session->xdr,&req)) goto xdr_error;

	if ( (r=ndmp_send_xdr_buffer(session))) return r;

	if  ( (r=ndmp_recv_header(session,session->seq_number))) return r;

	if ( (r=ndmp_recv_basic_response(session,sizeof(ndmp_connect_client_auth_reply))) ) return r;

	if ( ! xdr_ndmp_connect_client_auth_reply(&session->xdr,&rep) ) goto xdr_error;

	if ( rep.error ) {
		printk("ndmphba:%d: NDMP Authentication failed, Error: %s\n", session->session_id, ndmperror(rep.error));
       		ndmp_change_session_state(session, HBA_STATE_AUTH,_NDMP_STAT_FAILED);
		ndmp_close_socket(session);
		return  NDMP_SCSI_AUTH_FAILED;
	}

	ndmp_change_session_state(session, HBA_STATE_AUTH,_NDMP_STAT_DONE);
	printk("ndmphba:%d: Authenticated to NDMP Server\n", session->session_id);

	return 0;

xdr_error:
	printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_auth\n", session->session_id);
       ndmp_change_session_state(session, HBA_STATE_AUTH,_NDMP_STAT_FAILED);
       ndmp_close_socket(session);
       return NDMP_XDR_ERROR;
	
}

/*
 * ndmp_send_xdr_buffer - sends the XDR buffer along with IOV associated with the message
 */
static int ndmp_send_xdr_buffer(struct ndmp_scsi_session *session)
{
	int r;
	XDR *xdr;
	struct kvec kv;
	int len;

	xdr=&session->xdr;
        kv.iov_len= xdr->pos;
        kv.iov_base = xdr->buffer;

	if ( xdr->kvcount)  len =session->xdr.pos+session->xdr.kvlen-4;
	else len = session->xdr.pos-4;

        len |= 0x80000000;
        len=htonl(len);
        memcpy(xdr->buffer,&len,4);

        if (( r=ndmp_sendmsg(session, &kv, 1, kv.iov_len))) return r;
	if ( xdr->kvcount ) {
		r=ndmp_sendmsg(session, xdr->kv, xdr->kvcount,  xdr->kvlen);
	}
	return r;


}

/*
 * ndmp_tape_open - Open the NDMP tape drive
 *
 */
static int ndmp_tape_open ( struct ndmp_scsi_session *session)
{

        ndmp_tape_open_request req;
        ndmp_tape_open_reply rep;
	int r;
	int code;



	session->seq_number++;

	if ( session->device_type == TAPE_DEVICE ) code=NDMP_TAPE_OPEN;
	else  code=NDMP_SCSI_OPEN;

        req.device=session->scsi_device;
	req.mode=NDMP_TAPE_RAW_MODE;

	if ( ndmp_prepare_ndmp_head(session,code,session->seq_number) ) goto xdr_error;
	if ( ! xdr_ndmp_tape_open_request(&session->xdr,&req, session->device_type)) goto xdr_error;
	if ( (r=ndmp_send_xdr_buffer(session)) ) return r;

	if  ( (r=ndmp_recv_header(session,session->seq_number))) return r;

	if ( (r=ndmp_recv_basic_response(session,sizeof(ndmp_tape_open_reply))) ) return r;

	if ( ! xdr_ndmp_tape_open_reply(&session->xdr,&rep) ) goto xdr_error;

	if ( rep.error ) {
		printk("ndmphba:%d: Unable to open the tape drive, Error: %s\n",session->session_id,ndmperror(rep.error));
		ndmp_close_socket(session);
       		ndmp_change_session_state(session, HBA_STATE_DEVICE,  _NDMP_STAT_FAILED);
		return  NDMP_SCSI_DEVICE_FAILED;
	} 

	ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_DONE);

        return 0;

xdr_error:
	printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_open_device\n", session->session_id);
	ndmp_change_session_state(session, HBA_STATE_DEVICE,_NDMP_STAT_FAILED);
       	ndmp_close_socket(session);
       	return NDMP_XDR_ERROR;


}

/*
 * ndmp_recv_connected - Handle the connected message from the NDMP server
 * this is a special message from the NMDP server as soon as the socket is connected
 */
static int ndmp_recv_connected (struct ndmp_scsi_session *session) 
{

	ndmp_notify_connection_status_post rec;
	XDR *xdr;
	int r;

	xdr=&session->xdr;

	if (( r=__ndmp_recv_header(session) ) ) return r;

	if ( (r=ndmp_recv_basic_response(session,sizeof(ndmp_notify_connection_status_post))) ) return r;
	if ( ! xdr_ndmp_notify_connection_status_post( xdr, &rec) ) goto xdr_error;
	printk("ndmphba:%d: Connect reason: %d / NDMP Server Version :%d\n", session->session_id,rec.reason, rec.protocol_version);

	return 0;

xdr_error:
	printk("ndmphba:%d: NDMP XDR encode/decode error in ndmp_recv_connected\n", session->session_id);
       ndmp_close_socket(session);
       return NDMP_XDR_ERROR;

}

/* ndmperror - Returns the ndmp error string for the ndmp error code.
 * Note that this function returns a static variable, I do not like dynamically allocating memory just for an error message, as 
 * it may lead to memory leak due to multiple exit points of the functions calling this routine.
 * after all it just an error message, if multiple functions call this at same time, unpredicable results!
 *
 */
static char *ndmperror(int code)
{
	static char error_s[32];

	char *error[32] = { 	"NDMP_NO_ERR", 
				"NDMP_NOT_SUPPORTED_ERR",
				"NDMP_DEVICE_BUSY_ERR",
				"NDMP_DEVICE_OPENED_ERR",
        			"NDMP_NOT_AUTHORIZED_ERR",
        			"NDMP_PERMISSION_ERR",
			        "NDMP_DEV_NOT_OPEN_ERR",
        			"NDMP_IO_ERR",
        			"NDMP_TIMEOUT_ERR",
        			"NDMP_ILLEGAL_ARGS_ERR",
        			"NDMP_NO_TAPE_LOADED_ERR",
        			"NDMP_WRITE_PROTECT_ERR",
        			"NDMP_EOF_ERR",
        			"NDMP_EOM_ERR",
        			"NDMP_FILE_NOT_FOUND_ERR",
        			"NDMP_BAD_FILE_ERR",
        			"NDMP_NO_DEVICE_ERR",
			        "NDMP_NO_BUS_ERR",
			        "NDMP_XDR_DECODE_ERR",
			        "NDMP_ILLEGAL_STATE_ERR",
			        "NDMP_UNDEFINED_ERR",
			        "NDMP_XDR_ENCODE_ERR",
			        "NDMP_NO_MEM_ERR",
			        "NDMP_CONNECT_ERR",
			        "NDMP_SEQUENCE_NUM_ERR",
			        "NDMP_READ_IN_PROGRESS_ERR",
			        "NDMP_PRECONDITION_ERR",
			        "NDMP_CLASS_NOT_SUPPORTED_ERR",
			        "NDMP_VERSION_NOT_SUPPORTED_ERR",
			        "NDMP_EXT_DUPL_CLASSES_ERR",
			        "NDMP_EXT_DANDN_ILLEGAL_ERR",
				""
			};

	if ( code > 30 ) code=31;
	strcpy(error_s,error[code]);
	return error_s;
}

/* ndmp_close_socket - close the NDMP socket */
int ndmp_close_socket( struct ndmp_scsi_session *session)
{
	spin_lock(&session->task_lock);
	if ( session->socket) sock_release(session->socket);
	__ndmp_change_session_state(session, HBA_STATE_CONNECTION, _NDMP_STAT_NR);
	session->socket=NULL;
	ndmp_kunmap(&session->xdr);
	spin_unlock(&session->task_lock);
	printk("ndmphba:%d:Socket closed\n", session->session_id);
	return 0;
}
